Skip to main content

Chapter 5: Modules & Namespaces

Authored by syscook.dev

What are Modules and Namespaces?

Modules and namespaces in TypeScript provide ways to organize and structure code into logical units, preventing naming conflicts and enabling code reusability across different parts of an application.

Key Concepts:

  • Modules: Self-contained units of code that can be imported and exported
  • Namespaces: TypeScript-specific way to organize code and avoid global namespace pollution
  • Import/Export: Mechanisms for sharing code between modules
  • Module Resolution: How TypeScript finds and loads modules
  • Declaration Files: Type definitions for JavaScript libraries

Why Use Modules and Namespaces?

1. Code Organization

Modules help organize code into logical, maintainable units.

// math.ts - Math utilities module
export function add(a: number, b: number): number {
return a + b;
}

export function multiply(a: number, b: number): number {
return a * b;
}

export const PI = 3.14159;

// Usage in another file
import { add, multiply, PI } from './math';

console.log("Addition:", add(5, 3));
console.log("Multiplication:", multiply(4, 7));
console.log("PI:", PI);

Output:

Addition: 8
Multiplication: 28
PI: 3.14159

2. Avoiding Naming Conflicts

Namespaces prevent global namespace pollution and naming conflicts.

// Without namespace - potential conflicts
// function process() { ... }
// function validate() { ... }

// With namespace - organized and conflict-free
namespace UserManagement {
export function process(user: any): void {
console.log("Processing user:", user);
}

export function validate(user: any): boolean {
return user && user.name && user.email;
}
}

namespace ProductManagement {
export function process(product: any): void {
console.log("Processing product:", product);
}

export function validate(product: any): boolean {
return product && product.name && product.price;
}
}

// Usage
const user = { name: "Alice", email: "[email protected]" };
const product = { name: "Laptop", price: 999 };

UserManagement.process(user);
ProductManagement.process(product);

console.log("User valid:", UserManagement.validate(user));
console.log("Product valid:", ProductManagement.validate(product));

Output:

Processing user: { name: 'Alice', email: '[email protected]' }
Processing product: { name: 'Laptop', price: 999 }
User valid: true
Product valid: true

How to Use Modules and Namespaces?

1. ES6 Modules

Basic Export/Import

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

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

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

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

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

// Default export
export default class UserRepository {
private users: User[] = [];

save(user: User): User {
this.users.push(user);
return user;
}
}

// Usage in another file
import UserRepository, { User, UserService } from './user';

const userRepo = new UserRepository();
const userService = new UserService();

const newUser: User = {
id: 1,
name: "John",
email: "[email protected]"
};

userRepo.save(newUser);
userService.addUser(newUser);

console.log("All users:", userService.getAllUsers());

Output:

All users: [{ id: 1, name: 'John', email: '[email protected]' }]

Re-exporting and Barrel Exports

// math/basic.ts
export function add(a: number, b: number): number {
return a + b;
}

export function subtract(a: number, b: number): number {
return a - b;
}

// math/advanced.ts
export function power(base: number, exponent: number): number {
return Math.pow(base, exponent);
}

export function sqrt(value: number): number {
return Math.sqrt(value);
}

// math/index.ts - Barrel export
export * from './basic';
export * from './advanced';

// Usage
import { add, subtract, power, sqrt } from './math';

console.log("Add:", add(5, 3));
console.log("Subtract:", subtract(10, 4));
console.log("Power:", power(2, 3));
console.log("Square root:", sqrt(16));

Output:

Add: 8
Subtract: 6
Power: 8
Square root: 4

2. Namespaces

Basic Namespace

namespace Geometry {
export interface Point {
x: number;
y: number;
}

export class Circle {
constructor(
public center: Point,
public radius: number
) {}

getArea(): number {
return Math.PI * this.radius * this.radius;
}

getCircumference(): number {
return 2 * Math.PI * this.radius;
}
}

export class Rectangle {
constructor(
public topLeft: Point,
public bottomRight: Point
) {}

getArea(): number {
const width = this.bottomRight.x - this.topLeft.x;
const height = this.bottomRight.y - this.topLeft.y;
return width * height;
}

getPerimeter(): number {
const width = this.bottomRight.x - this.topLeft.x;
const height = this.bottomRight.y - this.topLeft.y;
return 2 * (width + height);
}
}
}

// Usage
const center: Geometry.Point = { x: 0, y: 0 };
const circle = new Geometry.Circle(center, 5);
const rectangle = new Geometry.Rectangle(
{ x: 0, y: 0 },
{ x: 10, y: 5 }
);

console.log("Circle area:", circle.getArea());
console.log("Circle circumference:", circle.getCircumference());
console.log("Rectangle area:", rectangle.getArea());
console.log("Rectangle perimeter:", rectangle.getPerimeter());

Output:

Circle area: 78.53981633974483
Circle circumference: 31.41592653589793
Rectangle area: 50
Rectangle perimeter: 30

Nested Namespaces

namespace Company {
export namespace HR {
export interface Employee {
id: number;
name: string;
department: string;
}

export class EmployeeService {
private employees: Employee[] = [];

addEmployee(employee: Employee): void {
this.employees.push(employee);
}

getEmployeesByDepartment(department: string): Employee[] {
return this.employees.filter(emp => emp.department === department);
}
}
}

export namespace Finance {
export interface Salary {
employeeId: number;
amount: number;
currency: string;
}

export class PayrollService {
private salaries: Salary[] = [];

addSalary(salary: Salary): void {
this.salaries.push(salary);
}

getTotalPayroll(): number {
return this.salaries.reduce((total, salary) => total + salary.amount, 0);
}
}
}
}

// Usage
const hrService = new Company.HR.EmployeeService();
const payrollService = new Company.Finance.PayrollService();

const employee: Company.HR.Employee = {
id: 1,
name: "Alice",
department: "Engineering"
};

const salary: Company.Finance.Salary = {
employeeId: 1,
amount: 75000,
currency: "USD"
};

hrService.addEmployee(employee);
payrollService.addSalary(salary);

console.log("Engineering employees:", hrService.getEmployeesByDepartment("Engineering"));
console.log("Total payroll:", payrollService.getTotalPayroll());

Output:

Engineering employees: [{ id: 1, name: 'Alice', department: 'Engineering' }]
Total payroll: 75000

3. Module Resolution

Relative and Absolute Imports

// Relative imports
import { User } from './user'; // Same directory
import { Product } from '../models/product'; // Parent directory
import { Utils } from './utils/helpers'; // Subdirectory

// Absolute imports (with path mapping in tsconfig.json)
import { ApiClient } from '@api/client';
import { Database } from '@database/connection';

// Node modules
import * as fs from 'fs';
import { EventEmitter } from 'events';

Path Mapping Configuration

// tsconfig.json
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@/*": ["*"],
"@api/*": ["api/*"],
"@models/*": ["models/*"],
"@utils/*": ["utils/*"]
}
}
}

4. Declaration Files

Creating Declaration Files

// types/global.d.ts
declare global {
interface Window {
myCustomProperty: string;
}

namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
API_URL: string;
DATABASE_URL: string;
}
}
}

// types/library.d.ts
declare module 'custom-library' {
export interface CustomConfig {
apiKey: string;
timeout: number;
}

export class CustomClient {
constructor(config: CustomConfig);
request(endpoint: string): Promise<any>;
}
}

// Usage
const client = new CustomClient({
apiKey: 'your-api-key',
timeout: 5000
});

console.log("Environment:", process.env.NODE_ENV);
console.log("API URL:", process.env.API_URL);

Output:

Environment: development
API URL: https://api.example.com

Practical Examples

1. Modular E-commerce System

// models/Product.ts
export interface Product {
id: number;
name: string;
price: number;
category: string;
inStock: boolean;
}

export class ProductModel {
private products: Product[] = [];

addProduct(product: Product): void {
this.products.push(product);
}

getProductById(id: number): Product | undefined {
return this.products.find(p => p.id === id);
}

getProductsByCategory(category: string): Product[] {
return this.products.filter(p => p.category === category);
}
}

// services/CartService.ts
import { Product } from '../models/Product';

export interface CartItem {
product: Product;
quantity: number;
}

export class CartService {
private items: CartItem[] = [];

addItem(product: Product, quantity: number = 1): void {
const existingItem = this.items.find(item => item.product.id === product.id);

if (existingItem) {
existingItem.quantity += quantity;
} else {
this.items.push({ product, quantity });
}
}

removeItem(productId: number): void {
this.items = this.items.filter(item => item.product.id !== productId);
}

getTotal(): number {
return this.items.reduce((total, item) => {
return total + (item.product.price * item.quantity);
}, 0);
}

getItems(): CartItem[] {
return [...this.items];
}
}

// Main application
import { ProductModel, Product } from './models/Product';
import { CartService } from './services/CartService';

const productModel = new ProductModel();
const cartService = new CartService();

// Add products
const laptop: Product = {
id: 1,
name: "Gaming Laptop",
price: 1500,
category: "Electronics",
inStock: true
};

const mouse: Product = {
id: 2,
name: "Wireless Mouse",
price: 50,
category: "Electronics",
inStock: true
};

productModel.addProduct(laptop);
productModel.addProduct(mouse);

// Add to cart
cartService.addItem(laptop, 1);
cartService.addItem(mouse, 2);

console.log("Cart items:", cartService.getItems());
console.log("Total:", cartService.getTotal());

Output:

Cart items: [
{ product: { id: 1, name: 'Gaming Laptop', price: 1500, category: 'Electronics', inStock: true }, quantity: 1 },
{ product: { id: 2, name: 'Wireless Mouse', price: 50, category: 'Electronics', inStock: true }, quantity: 2 }
]
Total: 1600

2. Namespace-based Utility Library

namespace Utils {
export namespace String {
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}

export function reverse(str: string): string {
return str.split('').reverse().join('');
}

export function truncate(str: string, length: number): string {
return str.length > length ? str.substring(0, length) + '...' : str;
}
}

export namespace Array {
export function unique<T>(arr: T[]): T[] {
return [...new Set(arr)];
}

export function shuffle<T>(arr: T[]): T[] {
const shuffled = [...arr];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
}

export function groupBy<T, K extends keyof T>(arr: T[], key: K): Record<string, T[]> {
return arr.reduce((groups, item) => {
const group = String(item[key]);
groups[group] = groups[group] || [];
groups[group].push(item);
return groups;
}, {} as Record<string, T[]>);
}
}

export namespace Date {
export function format(date: Date, format: string = 'YYYY-MM-DD'): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');

return format
.replace('YYYY', String(year))
.replace('MM', month)
.replace('DD', day);
}

export function addDays(date: Date, days: number): Date {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
}
}

// Usage
const text = "hello world";
const numbers = [1, 2, 2, 3, 3, 4, 5];
const today = new Date();

console.log("Capitalized:", Utils.String.capitalize(text));
console.log("Reversed:", Utils.String.reverse(text));
console.log("Truncated:", Utils.String.truncate("This is a long string", 10));

console.log("Unique numbers:", Utils.Array.unique(numbers));
console.log("Shuffled:", Utils.Array.shuffle(numbers));

const users = [
{ name: "Alice", department: "Engineering" },
{ name: "Bob", department: "Marketing" },
{ name: "Charlie", department: "Engineering" }
];

console.log("Grouped by department:", Utils.Array.groupBy(users, 'department'));

console.log("Formatted date:", Utils.Date.format(today));
console.log("Date + 7 days:", Utils.Date.format(Utils.Date.addDays(today, 7)));

Output:

Capitalized: Hello world
Reversed: dlrow olleh
Truncated: This is a...
Unique numbers: [1, 2, 3, 4, 5]
Shuffled: [3, 1, 5, 2, 4, 2, 3]
Grouped by department: {
Engineering: [
{ name: 'Alice', department: 'Engineering' },
{ name: 'Charlie', department: 'Engineering' }
],
Marketing: [{ name: 'Bob', department: 'Marketing' }]
}
Formatted date: 2024-01-15
Date + 7 days: 2024-01-22

Best Practices

1. Use Modules for Modern Code

// Good: Use ES6 modules
export interface User {
id: number;
name: string;
}

export class UserService {
// Implementation
}

// Good: Use barrel exports
export * from './user';
export * from './product';
export * from './order';

2. Use Namespaces for Legacy Code

// Good: Use namespaces for organizing legacy code
namespace LegacySystem {
export class OldService {
// Legacy implementation
}
}

3. Consistent Import/Export Patterns

// Good: Consistent import patterns
import { User, UserService } from './user';
import { Product, ProductService } from './product';

// Good: Use default exports sparingly
export default class MainApplication {
// Main application class
}

4. Proper Declaration Files

// Good: Well-structured declaration files
declare module 'external-library' {
export interface Config {
apiKey: string;
timeout: number;
}

export class Client {
constructor(config: Config);
request(url: string): Promise<any>;
}
}

Common Pitfalls and Solutions

1. Circular Dependencies

// ❌ Problem: Circular dependency
// user.ts
import { Product } from './product';
export class User { /* uses Product */ }

// product.ts
import { User } from './user';
export class Product { /* uses User */ }

// ✅ Solution: Use interfaces or dependency injection
// user.ts
export interface IProduct {
id: number;
name: string;
}

export class User {
constructor(private product: IProduct) {}
}

// product.ts
export interface IUser {
id: number;
name: string;
}

export class Product {
constructor(private user: IUser) {}
}

2. Namespace vs Module Confusion

// ❌ Problem: Mixing namespaces and modules
namespace MyNamespace {
export class MyClass {}
}

import { MyClass } from './mynamespace'; // Error

// ✅ Solution: Use consistent approach
// Option 1: Pure modules
export class MyClass {}

// Option 2: Pure namespaces
namespace MyNamespace {
export class MyClass {}
}

Conclusion

Modules and namespaces in TypeScript provide essential tools for organizing and structuring code. By understanding:

  • What modules and namespaces are and their purposes
  • Why they're important for code organization and avoiding conflicts
  • How to use import/export, namespaces, and declaration files

You can create well-organized, maintainable applications with clear separation of concerns. Modern TypeScript development favors ES6 modules, while namespaces remain useful for organizing legacy code and avoiding global namespace pollution.

Next Steps

  • Practice creating modular applications
  • Experiment with different import/export patterns
  • Learn about module bundlers and build tools
  • Move on to Chapter 6: Decorators

This tutorial is part of the TypeScript Mastery series by syscook.dev