🧱Type System Fundamentals

Master the powerful type system that sets TypeScript apart.

πŸ”‘ Primitive and Special Types

In NestJS and TypeScript, understanding primitive types is foundational. These are the basic building blocks for data in your applications. Let's explore each type and how they play a role in creating robust software.

πŸ’‘ Primitive Types

  • string: Represents textual data, like names or messages.
  • number: Handles numeric values, both integers and decimals.
  • boolean: Represents true/false values for logical operations.
  • undefined: The absence of a value, often used in function parameters.
  • null: Explicitly represents the absence of an object value.

πŸ’‘ Special Types

  • any: A fallback type when other types are not suitable. Use with caution.
  • unknown: Similar to any, but safer as it requires type checking before use.
  • never: Represents a value that never occurs, useful for functions that throw errors.

βœ… Working with Unions

Union types allow you to specify that a variable can be one of several types. This flexibility is powerful but should be used thoughtfully to maintain type safety.

function greet(name: string | null): string {
  if (name === null) {
    return 'Hello, stranger!';
  }
  return `Hello, ${name}!`;
}

βœ… Best Practices for Type Safety

  • Always specify the most specific type possible.
  • Avoid overusing any and unknown unless necessary.
  • Use unions only when multiple types are explicitly expected.
  • Leverage TypeScript's strict mode to catch potential issues early.

❌ Common Pitfalls to Avoid

  • Don't mix incompatible types in unions without careful consideration.
  • Avoid using never as a return type unless the function truly never returns.
  • Never assume a variable is of a certain type without proper checking.

πŸ“š Type Inference and Annotations

Welcome to the world of TypeScript! In this chapter, we'll explore how TypeScript handles type inference and when you need to use explicit type annotations. Understanding these concepts is crucial for writing clean, maintainable code in your NestJS applications.

πŸ’‘ What is Type Inference?

Type inference is TypeScript's ability to automatically deduce the type of a variable based on its initial value. This feature allows you to write more concise code while maintaining strong typing.

βœ… How Does Type Inference Work?

When you declare a variable without an explicit type, TypeScript infers its type from the initial value. For example:

let age = 25; // inferred as number
let name = 'Alice'; // inferred as string

πŸ’‘ Explicit Type Annotations

While type inference is powerful, there are cases where you must provide explicit type annotations. These include situations with union types, intersection types, or when TypeScript cannot reliably infer the type.

let value: number | string = 'Hello';
interface User {
  name: string;
  age: number;
}
let user: User;

βœ… Best Practices for Type Annotations

  • Use explicit annotations when improving readability or preventing type errors.
  • Annotate function parameters and return types for better documentation.
  • Avoid over-annotating - rely on inference where possible.

❌ Common Pitfalls with Type Inference

Be cautious of TypeScript's implicit 'any' type when it cannot determine a specific type. For example:

// Bad - implicit any
let data = {}; // inferred as {} but can be modified freely

data.name = 123; // No error!

data = [1, 2, 3]; // No error!

πŸ’‘ Managing Implicit 'any'

To avoid implicit any, enable the 'strict' compiler option and use explicit annotations for complex or generic types.

// Good - explicit annotation
interface Data {
  name?: string;
}

let data: Data = {};

πŸ“¦ Arrays, Tuples, and Enums

Understanding arrays, tuples, and enums in NestJS is essential for building robust TypeScript applications. These data structures allow you to model your data more precisely and catch errors early through static type checking.

πŸ’‘ Arrays: Flexible Data Collections

An array is a collection of elements that share the same data type. Arrays are useful when you need to store multiple values in a single variable.

const numbers: number[] = [1, 2, 3];
const strings: string[] = ['a', 'b', 'c'];
  • Arrays can contain elements of the same type
  • Useful for collections of items with identical properties
  • Dynamic length that can grow or shrink
  • Ideal for scenarios like lists, queues, and stacks

βœ… Tuples: Fixed-Length Structures

A tuple is an array with a fixed number of elements where each element has a specific type. Tuples are useful when you need to maintain the order and type of elements.

const user: [string, number] = ['Alice', 30];
  • Fixed length ensures data integrity
  • Maintains order and type of elements
  • Useful for pairs like coordinates or key-value stores
  • Great for representing records or structured data

πŸ’‘ Enums: Named Constants

An enum (enumeration) is a way to define a set of named constants. Enums make your code more readable and maintainable by assigning meaningful names to values.

enum Status {
  PENDING = 'pending',
  APPROVED = 'approved',
  REJECTED = 'rejected'
}
  • Improves code readability by using meaningful names
  • Ensures values are consistent throughout the application
  • Useful for state management and configuration
  • Can be backed by numbers or strings

βœ… Best Practices When Using Arrays, Tuples, and Enums

  • Use arrays for collections of homogeneous data
  • Prefer tuples when the order and type of elements matter
  • Define enums for sets of related constants
  • Take advantage of TypeScript's union types with arrays and tuples

πŸ’‘ Real-World Applications

These type systems are essential in building robust APIs. For example, you might use an array to store a list of users, a tuple to represent a user's coordinates (latitude and longitude), or an enum to define HTTP status codes.

// Example: Using all three together
enum HttpStatusCode {
  OK = 200,
  NOT_FOUND = 404,
  INTERNAL_ERROR = 500
}

type ApiResponse<T> = [HttpStatusCode, T | null];

const response: ApiResponse<string> = [HttpStatusCode.OK, 'Success'];

🧩 Type Aliases and Interfaces

Welcome to our chapter on Type Aliases and Interfaces in NestJS! In this section, we will explore two fundamental TypeScript features that help you create more maintainable and scalable code.

πŸ’‘ What are Type Aliases?

Type aliases allow you to create new names for existing types. They are particularly useful when working with complex or frequently used types.

type UserId = string;

const user: { id: UserId, name: string };
  • Simplify complex type definitions
  • Create reusable type names
  • Enhance code readability

πŸ’‘ What are Interfaces?

Interfaces define the shape of an object by specifying its properties and their types. They are essential for enforcing consistent data structures throughout your application.

interface User {
  id: string;
  name: string;
  email: string;
}

const user: User;
  • Define object structure
  • Enforce data consistency
  • Support declaration merging

πŸ’‘ Key Differences Between Type Aliases and Interfaces

While both type aliases and interfaces serve to define types, they have distinct use cases and capabilities.

  • Type aliases can be used for any existing type (e.g., primitives, unions, intersections)
  • Interfaces are specifically designed for object structures
  • Interfaces support declaration merging while type aliases do not
interface Point {
  x: number;
  y: number;
}

// Later in the codebase, you can extend this interface:
interface Point {
  z: number; // Adds a new property
}

βœ… Best Practices for Using Type Aliases and Interfaces

  • Use type aliases for simple or frequently reused types
  • Use interfaces for object structures with multiple properties
  • Prefer interfaces when you need to support declaration merging
  • Avoid unnecessary complexity in your type definitions

❌ Common Mistakes to Avoid

  • Don't use type aliases for object structures that require declaration merging
  • Avoid creating overly generic interfaces
  • Don't mix type aliases and interfaces unnecessarily

Quiz

Question 1 of 17

Which primitive type is used to represent textual data?

  • string
  • number
  • boolean
  • undefined