intermediate Step 12 of 18

Modules and Namespaces

TypeScript Programming

Modules and Namespaces

As TypeScript projects grow, organizing code into separate files and modules becomes essential for maintainability. TypeScript supports the standard ES module system with import and export statements, which is the recommended approach for modern applications. Namespaces (formerly called internal modules) are an older TypeScript-specific pattern for organizing code within a single compilation scope. Understanding both is important, but new projects should default to ES modules since they are the standard in both browser and Node.js environments.

Exporting from Modules

Any declaration — variable, function, class, interface, or type — can be exported from a module using the export keyword. A module is simply any file that contains an import or export statement.

// models/user.ts
export interface User {
  id: number;
  name: string;
  email: string;
}

export type UserRole = "admin" | "editor" | "viewer";

export function createUser(name: string, email: string): User {
  return {
    id: Math.floor(Math.random() * 10000),
    name,
    email,
  };
}

export class UserService {
  private users: User[] = [];

  add(user: User): void {
    this.users.push(user);
  }

  findById(id: number): User | undefined {
    return this.users.find(u => u.id === id);
  }

  getAll(): User[] {
    return [...this.users];
  }
}

Importing Modules

Import declarations bring exported bindings into the current module. TypeScript supports named imports, default imports, namespace imports, and re-exports.

// app.ts
// Named imports
import { User, UserRole, createUser, UserService } from "./models/user";

// Rename import to avoid conflicts
import { User as UserModel } from "./models/user";

// Import everything as a namespace
import * as UserModule from "./models/user";
const user: UserModule.User = UserModule.createUser("Alice", "a@test.com");

// Default export and import
// logger.ts
export default class Logger {
  log(message: string): void {
    console.log(`[LOG] ${message}`);
  }
}
// app.ts
import Logger from "./logger";
const logger = new Logger();

Re-exports and Barrel Files

Barrel files re-export from multiple modules through a single entry point, simplifying import paths for consumers of your library or module group.

// models/index.ts — barrel file
export { User, UserRole, createUser } from "./user";
export { Product, ProductCategory } from "./product";
export { Order, OrderStatus } from "./order";

// Now consumers can import from a single path
import { User, Product, Order } from "./models";

Type-Only Imports and Exports

TypeScript 3.8 introduced import type to explicitly mark imports that should be erased at runtime. This helps bundlers with tree-shaking and makes intent clear.

// Only importing the type — no runtime code
import type { User, UserRole } from "./models/user";

// Mixed import — createUser is a value, User is a type
import { createUser, type User } from "./models/user";

function processUser(user: User): void {
  console.log(user.name);
}

Namespaces

Namespaces group related code under a named scope. They are primarily used in declaration files and legacy projects. Modern TypeScript projects should prefer ES modules.

namespace Validation {
  export interface Rule {
    name: string;
    validate(value: string): boolean;
  }

  export class EmailRule implements Rule {
    name = "email";
    validate(value: string): boolean {
      return /^[^@]+@[^@]+\.[^@]+$/.test(value);
    }
  }

  export class MinLengthRule implements Rule {
    constructor(public name: string, private min: number) {}
    validate(value: string): boolean {
      return value.length >= this.min;
    }
  }
}

// Use with namespace prefix
const emailRule = new Validation.EmailRule();
console.log(emailRule.validate("test@example.com")); // true

Module Resolution

TypeScript resolves module paths according to the moduleResolution setting in tsconfig.json. The node strategy mirrors Node.js resolution, and bundler (TypeScript 5.0+) works best with modern bundlers like Vite or webpack.

{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "bundler",
    "baseUrl": "./src",
    "paths": {
      "@models/*": ["models/*"],
      "@utils/*": ["utils/*"]
    }
  }
}

// With path aliases configured
import { User } from "@models/user";
import { formatDate } from "@utils/date";
Tip: Always prefer ES modules over namespaces in new projects. Use barrel files (index.ts) to create clean public APIs for module groups, and use import type for type-only imports to improve build performance.

Key Takeaways

  • ES modules with import and export are the standard for organizing TypeScript code.
  • Named exports allow importing specific declarations; default exports provide a single main export.
  • Barrel files (index.ts) re-export from multiple modules for cleaner import paths.
  • Use import type for type-only imports that should be erased at compile time.
  • Namespaces are a legacy pattern; prefer ES modules for new code.