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