bugl
bugl
HomeLearnPatternsPathsSearchPremium
HomeLearnPatternsPaths
Learn/TypeScript/TypeScript Core
TypeScript•TypeScript Core

TypeScript Namespaces

Understanding TypeScript Namespaces

TypeScript namespaces (previously known as "internal modules") provide a powerful way to organize code and prevent naming conflicts by creating a container for related functionality.

They help in structuring large codebases and managing scope in a clean, maintainable way.

Key Concepts

  • Logical Grouping : Organize related code into named containers
  • Scope Management : Control the visibility of code elements
  • Name Collision Prevention : Avoid conflicts between similarly named components
  • Code Organization : Structure large applications in a hierarchical manner

When to Use Namespaces

  • Organizing code in large legacy applications
  • Working with global libraries
  • When migrating from older JavaScript codebases
  • When working with code that needs to be available globally

Note

While namespaces are still fully supported in TypeScript, modern applications typically use ES modules (import/export) for better modularity and tree-shaking support.

However, understanding namespaces is valuable for maintaining legacy codebases and certain library development scenarios.

Creating and Using Namespaces

A namespace is defined using the namespace keyword:

Example

namespace Validation {
  // Everything inside this block belongs to the Validation namespace // Export things you want to make available outside the namespace export interface StringValidator { isValid(s: string): boolean;
}
// This is private to the namespace (not exported)
const lettersRegexp = /^[A-Za-z]+$/;
// Exported class - available outside the namespace export class LettersValidator implements StringValidator { isValid(s: string): boolean {
return lettersRegexp.test(s);
}
}
// Another exported class export class ZipCodeValidator implements StringValidator { isValid(s: string): boolean {
return /^[0-9]+$/.test(s) && s.length === 5;
}
}
}
// Using the namespace members
let letterValidator = new Validation.LettersValidator();
let zipCodeValidator = new Validation.ZipCodeValidator();
console.log(letterValidator.isValid("Hello")); // true
console.log(letterValidator.isValid("Hello123")); // false
console.log(zipCodeValidator.isValid("12345")); // true
console.log(zipCodeValidator.isValid("1234")); // false - wrong length

Nested Namespaces

Namespaces can be nested to create hierarchical organization:

Example

namespace App {
  export namespace Utils {
    export function log(msg: string): void {
      console.log(`[LOG]: ${msg}`);
    }
  export function error(msg: string): void {
    console.error(`[ERROR]: ${msg}`);
  }
}
export namespace Models {
  export interface User {
    id: number;
    name: string;
    email: string;
  }
export class UserService {
  getUser(id: number): User {
    return { id, name: "John Doe", email: "john@example.com" };
  }
}
}
}
// Using nested namespaces App.Utils.log("Application starting");
const userService = new App.Models.UserService();
const user = userService.getUser(1);
App.Utils.log(`User loaded: ${user.name}`);
// This would be a type error in TypeScript // App.log("directly accessing log"); // Error - log is not a direct member of App

Namespace Aliases

You can create aliases for namespaces or their members to make long names more manageable:

Example

namespace VeryLongNamespace {
  export namespace DeeplyNested {
    export namespace Components {
      export class Button {
        display(): void {
          console.log("Button displayed");
        }
    }
  export class TextField {
    display(): void {
      console.log("TextField displayed");
    }
}
}
}
}
// Without alias - very verbose
const button1 = new VeryLongNamespace.DeeplyNested.Components.Button();
button1.display();
// With namespace alias import Components = VeryLongNamespace.DeeplyNested.Components;
const button2 = new Components.Button();
button2.display();
// With specific member alias import Button = VeryLongNamespace.DeeplyNested.Components.Button;
const button3 = new Button();
button3.display();

Splitting Namespaces Across Files

Large applications often require splitting code across multiple files.

TypeScript namespaces can be split across files and combined at compile time using reference comments:

Example

namespace Validation {
 export interface StringValidator {
 isValid(s: string): boolean;
 }
}

letters-validator.ts file (extends Validation namespace):

Example

/// <reference path="validators.ts" />
namespace Validation {
 const lettersRegexp = /^[A-Za-z]+$/;
 export class LettersValidator implements StringValidator {
 isValid(s: string): boolean {
 return lettersRegexp.test(s);
 }
 }
}

zipcode-validator.ts file:

Example

/// <reference path="validators.ts" />
namespace Validation {
 const zipCodeRegexp = /^[0-9]+$/;
 export class ZipCodeValidator implements StringValidator {
 isValid(s: string): boolean {
 return zipCodeRegexp.test(s) && s.length === 5;
 }
 }
}

main.ts file:

Example

/// <reference path="validators.ts" />
/// <reference path="letters-validator.ts" />
/// <reference path="zipcode-validator.ts" />
// Now you can use the validators from multiple files
let validators: { [s: string]: Validation.StringValidator } = {};
validators["letters"] = new Validation.LettersValidator();
validators["zipcode"] = new Validation.ZipCodeValidator();
// Some samples to validate
let strings = ["Hello", "98052", "101"];
// Validate each
strings.forEach(s => {
 for (let name in validators) {
 console.log(`"${s}" - ${validators[name].isValid(s) ? "matches" : "does not match"} ${name}`);
 }
});

To compile these files into a single JavaScript file, use:

Example

tsc --outFile sample.js main.ts

Key Differences

Understanding when to use namespaces versus modules is crucial for TypeScript development:

  • Modules are the preferred way to organize code in modern TypeScript applications
  • Namespaces are still useful for specific scenarios like declaration merging or working with legacy code
  • Modules have better tooling support and tree-shaking capabilities
  • Namespaces can be useful for creating global libraries

Here's a comparison of when to use namespaces versus ES modules:

Comparison Table

FeatureNamespacesES Modules (import/export)
Recommended scope/scaleSimpler setups, small apps, legacy codebasesModern apps of any size; preferred for new projects
Syntax and usageGlobal access via dot notation (e.g., MyNS.Member )Explicit import / export with file paths
Loading/bundlingNo loader required; can emit single file via --outFileTypically uses a bundler/loader (Vite, webpack, etc.)
Splitting across filesPossible via /// <reference /> commentsNatural; each file is a module with explicit exports/imports
Tree-shakingLimited; harder for bundlers to eliminate unused codeExcellent; designed for dead-code elimination
Global scopeEncourages globals (namespaced)Avoids globals; explicit dependencies
Augmentation/mergingStrong support via declaration mergingModule augmentation possible, but more constrained
Tooling and ecosystemWorks, but less aligned with modern toolingBest support across modern tooling and platforms
Best used forLegacy libraries, global scripts, ambient typesAll new development, libraries, and applications

Advanced Namespace Patterns

Example

// Original namespace
declare namespace Express {
 interface Request {
 user?: { id: number; name: string };
 }
 interface Response {
 json(data: any): void;
 }
}
// Later in your application (e.g., in a .d.ts file)
declare namespace Express {
 // Augment the Request interface
 interface Request {
 // Add custom properties
 requestTime?: number;
 // Add methods
 log(message: string): void;
 }
 // Add new types
 interface UserSession {
 userId: number;
 expires: Date;
 }
}
// Usage in your application
const app = express();
app.use((req: Express.Request, res: Express.Response, next) => {
 // Augmented properties and methods are available
 req.requestTime = Date.now();
 req.log('Request started');
 next();
});

Namespaces with Generics

Example

// Generic namespace example
namespace DataStorage {
 export interface Repository<T> {
 getAll(): T[];
 getById(id: number): T | undefined;
 add(item: T): void;
 update(id: number, item: T): boolean;
 delete(id: number): boolean;
 }
 // Concrete implementation
 export class InMemoryRepository<T> implements Repository<T> {
 private items: T[] = [];
 getAll(): T[] {
 return [...this.items];
 }
 getById(id: number): T | undefined {
 return this.items[id];
 }
 add(item: T): void {
 this.items.push(item);
 }
 update(id: number, item: T): boolean {
 if (id >= 0 && id < this.items.length) {
 this.items[id] = item;
 return true;
 }
 return false;
 }
 delete(id: number): boolean {
 if (id >= 0 && id < this.items.length) {
 this.items.splice(id, 1);
 return true;
 }
 return false;
 }
 }
}
// Usage
interface User {
 id: number;
 name: string;
 email: string;
}
const userRepo = new DataStorage.InMemoryRepository<User>();
userRepo.add({ id: 1, name: 'John Doe', email: 'john@example.com' });
const allUsers = userRepo.getAll();

Namespace Best Practices

  • Use meaningful, hierarchical namespace names
  • Export only what's needed from namespaces
  • Use /// <reference /> for ordering in multi-file namespaces
  • Consider using modules for new projects
  • Use const enums within namespaces for better performance
  • Document your namespaces with JSDoc comments
  • Create overly deep namespace hierarchies (more than 2-3 levels)
  • Pollute the global scope unnecessarily
  • Mix namespaces and modules in the same project without a clear strategy
  • Use namespaces for small applications - prefer modules

Performance Considerations

  • Large namespaces can increase bundle size
  • Consider code splitting for large applications
  • Be mindful of circular dependencies in complex namespace structures
  • Use const enum for better performance with constant values

Migrating from Namespaces to Modules

Example

// Before: Using namespaces
namespace MyApp {
 export namespace Services {
 export class UserService {
 getUser(id: number) { /* ... */ }
 }
 }
}
// After: Using ES modules
// services/UserService.ts
export class UserService {
 getUser(id: number) { /* ... */ }
}
// app.ts
import { UserService } from './services/UserService';
const userService = new UserService();

Migration Steps

  • Convert each namespace to a module file
  • Replace export with ES module exports
  • Update imports to use ES module syntax
  • Configure your build system to handle modules
  • Update tests to work with the new module structure
  • Consider using a bundler like webpack or Rollup
  • Update your tsconfig.json to use "module": "ESNext"

Migration Tools

  • ts-migrate - Automated migration tool from Facebook
  • tslint with no-namespace rule to detect namespaces
  • TypeScript's built-in refactoring tools

Previous

TypeScript Literal Types

Next

TypeScript Index Signatures

This chapter

Overview
40

Lessons

241m

Read time

1. TypeScript Tutorial2. TypeScript Introduction3. TypeScript Getting Started4. TypeScript Simple Types5. TypeScript Explicit Types and Inference6. TypeScript Special Types7. TypeScript Arrays8. TypeScript Tuples9. TypeScript Object Types10. TypeScript Enums11. TypeScript Type Aliases and Interfaces12. TypeScript Union Types13. TypeScript Functions14. TypeScript Casting15. TypeScript Classes16. TypeScript Basic Generics17. TypeScript Utility Types18. TypeScript Keyof19. TypeScript Null & Undefined20. TypeScript Definitely Typed21. TypeScript 5.x Updates22. TypeScript Configuration23. TypeScript with Node.js24. TypeScript with React25. TypeScript Tooling26. TypeScript Advanced Types27. TypeScript Type Guards28. TypeScript Conditional Types29. TypeScript Mapped Types30. TypeScript Type Inference31. TypeScript Literal Types32. TypeScript Namespaces33. TypeScript Index Signatures34. TypeScript Declaration Merging35. TypeScript Async Programming36. TypeScript Decorators37. TypeScript in JavaScript Projects (JSDoc)38. TypeScript Migration Guide39. TypeScript Error Handling40. TypeScript Best Practices

On this page

Understanding TypeScript NamespacesKey ConceptsWhen to Use NamespacesCreating and Using NamespacesNested NamespacesNamespace AliasesSplitting Namespaces Across FilesKey DifferencesComparison TableAdvanced Namespace PatternsNamespaces with GenericsNamespace Best PracticesPerformance ConsiderationsMigrating from Namespaces to ModulesMigration StepsMigration Tools